/* MonkeyTalk - a cross-platform functional testing tool
Copyright (C) 2012 Gorilla Logic, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package com.gorillalogic.monkeytalk.api.meta.tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;
import com.thoughtworks.qdox.model.JavaSource;
import com.thoughtworks.qdox.model.Type;
/**
* Generate the Meta API for Java and print it. This is used as input to be inserted into
* com.gorillalogic.monkeytalk.api.meta.API.
*/
public class MetaAPIGenerator {
/**
* When true, ignore all meta API sources in the {@code com.gorillalogic.monkeytalk.api.flex}
* package when generating the API.java class. NOTE: To fully ignore or un-ignore Flex, you must
* also see {@code JSAPIGenerator}.
*/
private static final boolean IGNORE_FLEX = true;
public static String generateComponents(File srcDir) {
JavaDocBuilder javadoc = new JavaDocBuilder();
javadoc.addSourceTree(srcDir);
Map<String, String> componentMap = new HashMap<String, String>();
StringBuilder sb = new StringBuilder();
for (JavaSource src : javadoc.getSources()) {
if (IGNORE_FLEX && src.getPackageName().endsWith(".flex")) {
// for (JavaClass clazz : src.getClasses()) {
// System.out.println("IGNORE " + clazz.getFullyQualifiedName());
// }
continue;
}
for (JavaClass clazz : src.getClasses()) {
if (clazz.getTagByName("ignoreMeta") != null) {
continue;
}
System.out.println(clazz.getFullyQualifiedName());
sb.append("components.add(new Component(\n ");
sb.append('"').append(clazz.getName()).append("\",\n ");
sb.append('"').append(cleanString(clazz.getComment())).append("\",\n ");
Type[] extendz = clazz.getImplements();
sb.append(
extendz == null || extendz.length == 0 || extendz[0].getJavaClass() == null
|| extendz[0].getJavaClass().getName().equalsIgnoreCase("mtobject") ? "null"
: '"' + extendz[0].getJavaClass().getName().toString() + '"')
.append(",\n ");
StringBuilder actions = new StringBuilder();
for (JavaMethod meth : clazz.getMethods()) {
if (meth.getTagByName("ignoreMeta") != null) {
continue;
}
if (actions.length() > 0) {
actions.append(",\n");
}
actions.append(" new Action(\n ");
actions.append('"').append(upperCamel(meth.getName())).append("\",\n ");
actions.append('"').append(cleanString(meth.getComment()))
.append("\",\n ");
StringBuilder args = new StringBuilder();
for (DocletTag param : meth.getTagsByName("param")) {
String value = param.getValue();
int idx = value.indexOf("\n");
if (idx == -1) {
throw new RuntimeException("ERROR: " + clazz.getName() + "#"
+ meth.getName() + "() - bad @param javadoc");
}
String pname = value.substring(0, idx);
String pdesc = value.substring(idx + 1);
JavaParameter p = meth.getParameterByName(pname);
if (p == null) {
throw new RuntimeException("ERROR: " + clazz.getName() + "#"
+ meth.getName() + "() - @param " + pname
+ " has no matching method argument");
}
String ptype = p.getType().getJavaClass().getName();
if (args.length() > 0) {
args.append(",\n");
}
args.append(" new Arg(");
args.append('"').append(pname).append('"');
args.append(",\"").append(cleanString(pdesc)).append('"');
args.append(",\"").append(ptype).append('"');
args.append(p.isVarArgs() ? ",true" : "");
args.append(")");
}
if (args.length() > 0) {
actions.append("new ArrayList<Arg>(Arrays.asList(\n")
.append(args.toString()).append("\n )),\n ");
} else {
actions.append("null,\n ");
}
actions.append('"')
.append(meth.getReturnType() != null ? meth.getReturnType()
.getJavaClass().getName() : "").append("\",\n ");
actions.append('"')
.append(meth.getTagByName("return") != null ? cleanString(meth
.getTagByName("return").getValue()) : "").append("\")");
}
if (actions.length() > 0) {
sb.append("new ArrayList<Action>(Arrays.asList(\n").append(actions.toString())
.append("\n ))\n ,\n ");
} else {
sb.append("null,\n ");
}
StringBuilder properties = new StringBuilder();
for (DocletTag tag : clazz.getTagsByName("prop")) {
String val = tag.getParameters()[0];
String tagVal = tag.getValue();
int sep = tagVal.indexOf("-");
String desc = (sep != -1 ? tagVal.substring(sep + 1).trim() : null);
String args = (sep != -1 ? tagVal.substring(0, sep).trim() : tagVal);
args = (args.equals(val) ? null : args.substring(val.length()));
if (properties.length() > 0) {
properties.append(",\n");
}
properties.append(" new Property(");
properties.append("\"").append(val).append("\", ");
if (args == null) {
properties.append("null, ");
} else if (args.startsWith("(") && args.endsWith(")")) {
properties.append("\"")
.append(cleanString(args.substring(1, args.length() - 1)))
.append("\", ");
} else {
properties.append("\"").append(cleanString(args)).append("\", ");
}
if (desc == null) {
properties.append("null)");
} else {
properties.append("\"").append(cleanString(desc)).append("\")");
}
}
if (properties.length() > 0) {
sb.append("new ArrayList<Property>(Arrays.asList(\n")
.append(properties.toString()).append("\n ))\n )");
} else {
sb.append("null)");
}
sb.append("\n);\n");
componentMap.put(clazz.getName(), sb.toString());
sb = new StringBuilder();
}
}
// sort by component name (we do this now, during codegen, to avoid sorting later)
sb = new StringBuilder();
List<String> keys = new ArrayList<String>(componentMap.keySet());
Collections.sort(keys);
for (String k : keys) {
sb.append(componentMap.get(k));
}
return "IGNORE_FLEX = " + IGNORE_FLEX + ";\n\n" + sb.toString();
}
private static String cleanString(String s) {
if (s == null) {
return "";
}
return s.trim().replaceAll("\"", "'").replaceAll("\\n", " ").replaceAll("\\s+", " ");
}
private static String upperCamel(String s) {
if (s.length() < 2) {
return s.toUpperCase();
}
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
public static void main(String[] args) {
if (args.length != 4) {
System.err
.println("Usage: java MetaAPIGenerator <src dir> <target> <start tag> <end tag>");
System.exit(1);
}
File srcDir = new File(args[0]);
if (!srcDir.exists()) {
System.err.println("ERROR: srcDir '" + args[0] + "' not found");
System.err.println("ERROR: workingDir='" + new File(".").getAbsolutePath() + "'");
System.exit(1);
}
if (!srcDir.isDirectory()) {
System.err.println("ERROR: srcDir '" + srcDir.getAbsolutePath() + "' not directory");
System.exit(1);
}
File target = new File(args[1]);
if (!target.exists()) {
System.err.println("ERROR: target '" + args[1] + "' not found");
System.exit(1);
}
if (!target.isFile()) {
System.err.println("ERROR: target '" + args[1] + "' not file");
System.exit(1);
}
if (args[2].length() == 0) {
System.err.println("ERROR: start tag is blank");
System.exit(1);
}
if (args[3].length() == 0) {
System.err.println("ERROR: end tag is blank");
System.exit(1);
}
String components = generateComponents(srcDir);
try {
String orig = readFile(target);
int startTag = orig.indexOf(args[2]);
int endTag = orig.lastIndexOf(args[3]);
String out = orig.substring(0, startTag + args[2].length()) + "\n" + components
+ orig.substring(endTag);
writeFile(target, out);
} catch (IOException ex) {
ex.printStackTrace();
System.exit(1);
}
}
private static String readFile(File f) throws IOException {
StringBuilder sb = new StringBuilder();
Scanner scanner = new Scanner(new FileInputStream(f), "UTF-8");
try {
while (scanner.hasNextLine()) {
sb.append(scanner.nextLine()).append('\n');
}
} finally {
scanner.close();
}
return (sb.length() > 0 ? sb.substring(0, sb.length() - 1) : "");
}
private static void writeFile(File f, String contents) throws IOException {
Writer out = new OutputStreamWriter(new FileOutputStream(f), "UTF-8");
try {
out.write(contents);
} finally {
out.close();
}
}
}